import Editor from "../../editor";
import { Operation, SetSelectionOperation } from "../../operations";

class UndoList {
    private _editor: Editor;

    // 最大撤销步数
    private MAX_UNDO_NUMBER: number = 30;
    // 撤销合并间隔，300ms内的一起撤销
    private PUSH_GAP_TIME: number = 300;
    // 撤销数组，选区也要撤回，独立的光标变化可以抛弃
    private _undoList: Array<Array<Operation>>;
    // 当前撤销数组位置
    private _currentPos: number = -1;
    // 上次插入时间
    private _lastPushTime: number = 0;

    constructor(editor: Editor) {
        this._undoList = new Array();
        this._editor = editor;
    }

    // 如果上一次操作数组中全部都是光标变化，则该操作舍弃
    _checkAllSelectionOperation(): void {
        const curOperations: Array<Operation> | undefined = this._undoList[this._undoList.length - 1];
        if (curOperations == null) return;

        let allSelections = true;
        for (let i: number = 0; i < curOperations.length; i++) {
            if (!(curOperations[i] instanceof SetSelectionOperation)) {
                allSelections = false;
                break;
            }
        }
        if (allSelections) {
            this._currentPos = this._currentPos === this._undoList.length - 1 ? this._currentPos - 1 : this._currentPos;
            this._undoList.pop();
        }
    }

    // 如果当前撤销指针不在数组末尾，则之后的操作全部舍弃
    _checkIsUndoing(): void {
        if (this._undoList.length !== 0 && this._currentPos !== this._undoList.length - 1) {
            if (this._currentPos <= -1) {
                this._undoList = new Array();
            } else {
                this._undoList = this._undoList.slice(0, this._currentPos + 1);
            }
        }
    }

    pushOperation(operation: Operation): void {
        const curTime: number = Date.now();
        // 如果在300ms内操作，直接归入当前撤销数组
        if (curTime - this._lastPushTime <= this.PUSH_GAP_TIME) {
            this._undoList[this._undoList.length - 1].push(operation);
            if (!(operation instanceof SetSelectionOperation) &&
                this._undoList.length !== 0 && this._currentPos !== this._undoList.length - 1) {
                // 如果当前撤销指针不在数组末尾，则之后的操作全部舍弃
                const temp = this._undoList[this._undoList.length - 1].slice();
                this._checkIsUndoing();
                this._undoList.push(temp);
                this._currentPos = this._undoList.length - 1;
            }
        } else {
            // 如果上一次操作数组中全部都是光标变化，则该操作舍弃
            this._checkAllSelectionOperation();

            // 如果当前撤销数组已满，将最早记录的操作出列
            if (this._undoList.length >= this.MAX_UNDO_NUMBER) {
                this._undoList.shift();
                this._currentPos = this._currentPos - 1;
            }

            if (operation instanceof SetSelectionOperation) {
                this._undoList.push([operation]);
            } else {
                this._checkIsUndoing();
                this._undoList.push([operation]);
                this._currentPos = this._undoList.length - 1;
            }
        }

        this._lastPushTime = curTime;
        // console.log(this._undoList);
    }

    undoOperation() {
        this._checkAllSelectionOperation();

        const curOperations: Array<Operation> = this._undoList[this._currentPos];
        if (curOperations == null || curOperations.length === 0) {
            return;
        }

        for (let i: number = curOperations.length - 1; i >= 0; i--) {
            const operations: Operation[] = curOperations[i].reverse();
            for (let j: number = 0; j < operations.length; j++) {
                operations[j].apply(this._editor);
            }
        }

        this._currentPos = this._currentPos - 1;
    }

    redoOperstion() {
        if (this._undoList.length === 0 || this._currentPos >= this._undoList.length - 1) {
            return;
        }

        const curOperations: Array<Operation> = this._undoList[this._currentPos + 1];
        if (curOperations == null || curOperations.length === 0) {
            return;
        }

        for (let i: number = 0; i < curOperations.length; i++) {
            curOperations[i].apply(this._editor);
        }
        this._currentPos = this._currentPos + 1;
    }
}

export default function WithUndoRedo(editor: Editor) {
    const undoList = new UndoList(editor);

    const apply = editor.apply.bind(editor);
    editor.apply = (operation: Operation) => {
        undoList.pushOperation(operation);
        apply(operation);
    }

    // 撤销
    const undo = editor.undo.bind(editor);
    editor.undo = () => {
        undoList.undoOperation();
        undo();
    }

    // 回退
    const redo = editor.redo.bind(editor);
    editor.redo = () => {
        undoList.redoOperstion();
        redo();
    }
    return editor;
}